﻿namespace MicroCloud.Authentication.OAuth2.WeChat
{
    /// <summary>
    /// 微信认证处理器
    /// </summary>
    /// <remarks>
    /// 初始化一个微信认证处理器 <see cref="WeChatHandler"/> 的新实例
    /// </remarks>
    /// <param name="options"></param>
    /// <param name="logger"></param>
    /// <param name="encoder"></param>
    internal class WeChatHandler(IOptionsMonitor<WeChatOptions> options, ILoggerFactory logger, UrlEncoder encoder) : OAuthHandler<WeChatOptions>(options, logger, encoder)
    {
        #region "受保护的重写方法 - Overrides of OAuthHandler<QQOptions>"
        #region "Step 1：请求CODE，构建用户授权地址"
        /// <summary>
        /// Step 1：请求CODE，构建用户授权地址
        /// </summary>
        /// <param name="properties"></param>
        /// <param name="redirectUri"></param>
        /// <returns></returns>
        protected override string BuildChallengeUrl(AuthenticationProperties properties, string redirectUri)
        {
            return QueryHelpers.AddQueryString(Options.AuthorizationEndpoint, new Dictionary<string, string>
            {
                ["appid"] = Options.ClientId,
                ["redirect_uri"] = redirectUri,
                ["response_type"] = "code",
                ["scope"] = FormatScope(),
                // ["scope"] = WeChatDefaults.snsapi_login,
                ["state"] = Options.StateDataFormat.Protect(properties)
            });
        }
        #endregion
        #region "Step 2：通过code获取access_token"
        /// <summary>
        /// Step 2：通过code获取access_token
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        protected override async Task<OAuthTokenResponse> ExchangeCodeAsync(OAuthCodeExchangeContext context)
        {
            string code = context.Code;
            // string redirectUri = context.RedirectUri;

            var address = QueryHelpers.AddQueryString(Options.TokenEndpoint, new Dictionary<string, string>()
            {
                ["appid"] = Options.ClientId,
                ["secret"] = Options.ClientSecret,
                ["code"] = code,
                ["grant_type"] = "authorization_code",
                // ["redirect_uri"] = redirectUri
            });

            var response = await Backchannel.GetAsync(address);
            string responseContentJson = await response.Content.ReadAsStringAsync();
            if (!response.IsSuccessStatusCode)
            {
                Logger.LogError("检索访问令牌时出错：远程服务器返回了具有以下负载的{Status}响应：{Headers}{Body}.",
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ responseContentJson);

                return OAuthTokenResponse.Failed(new System.Exception("检索访问令牌时出错"));
            }

            var payload = JObject.Parse(responseContentJson);
            JsonDocument document = JsonDocument.Parse(responseContentJson);
            if (!string.IsNullOrEmpty(payload.Value<string>("errcode")))
            {
                Logger.LogError("检索访问令牌时出错：远程服务器返回了具有以下负载的{Status}响应：{Headers}{Body}.",
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ responseContentJson);

                return OAuthTokenResponse.Failed(new System.Exception("检索访问令牌时出错"));
            }

            return OAuthTokenResponse.Success(document);
        }
        #endregion
        #region "Step 3：从远程服务器创建票证"
        /// <summary>
        /// Step 3：从远程服务器创建票证
        /// </summary>
        /// <param name="identity"></param>
        /// <param name="properties"></param>
        /// <param name="tokens"></param>
        /// <returns></returns>
        protected override async Task<AuthenticationTicket> CreateTicketAsync(
            ClaimsIdentity identity,
            AuthenticationProperties properties,
            OAuthTokenResponse tokens)
        {
            tokens.Response.RootElement.TryGetProperty("openid", out JsonElement openIdElement);
            var openId = openIdElement.GetString();
            //var openId = JObject.Parse(tokens.Response.ToJsonString()).Value<string>("openid");
            var address = QueryHelpers.AddQueryString(Options.UserInformationEndpoint, new Dictionary<string, string>
            {
                ["access_token"] = tokens.AccessToken,
                ["openid"] = openId
            });

            var response = await Backchannel.GetAsync(address);
            string responseContentJson = await response.Content.ReadAsStringAsync();
            if (!response.IsSuccessStatusCode)
            {
                Logger.LogError("检索用户配置文件时出错：远程服务器返回了具有以下负载的{Status}响应：{Headers}{Body}.",
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ responseContentJson);

                throw new HttpRequestException("检索用户信息时出错");
            }

            var payload = JObject.Parse(responseContentJson);
            JsonDocument document = JsonDocument.Parse(responseContentJson);
            if (!string.IsNullOrEmpty(payload.Value<string>("errcode")))
            {
                Logger.LogError("检索用户配置文件时出错：远程服务器返回了具有以下负载的{Status}响应：{Headers}{Body}.",
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ responseContentJson);

                throw new HttpRequestException("检索用户信息时出错");
            }

            var userInfoDto = new WeChatUserInfoDto(payload);

            identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userInfoDto.UnionId, Options.ClaimsIssuer));
            identity.AddClaim(new Claim(ClaimTypes.Name, userInfoDto.NickName, Options.ClaimsIssuer));
            //identity.AddClaim(new Claim(ClaimTypes.Gender, ((int)userInfoDto.Gender).ToString(), Options.ClaimsIssuer));
            //identity.AddClaim(new Claim(ClaimTypes.Country, userInfoDto.Country, Options.ClaimsIssuer));

            //// identity.AddClaim(new Claim("urn:weixin:openid", userInfoDto.OpenId, Options.ClaimsIssuer));
            identity.AddClaim(new Claim(ClaimTypeConstants.OpenIdEx, userInfoDto.OpenId, Options.ClaimsIssuer));

            //// identity.AddClaim(new Claim("urn:weixin:province", userInfoDto.Province, Options.ClaimsIssuer));
            //identity.AddClaim(new Claim(ClaimTypes.StateOrProvince, userInfoDto.Province, Options.ClaimsIssuer));
            //identity.AddClaim(new Claim("urn:weixin:city", userInfoDto.City, Options.ClaimsIssuer));

            //// identity.AddClaim(new Claim("urn:weixin:headimgurl", userInfoDto.HeadImgUrl, Options.ClaimsIssuer));
            identity.AddClaim(new Claim(ClaimTypeConstants.HeadImgEx, userInfoDto.HeadImgUrl, Options.ClaimsIssuer));

            //identity.AddClaim(new Claim("urn:weixin:privilege", userInfoDto.Privilege.Join(","), Options.ClaimsIssuer));
            //identity.AddClaim(new Claim("urn:weixin:user_info", payload.ToString(), Options.ClaimsIssuer));

            ////var privilege = payload.Value<JArray>("privilege");
            ////if (privilege != null)
            ////{
            ////    payload.Add("urn:weixin:privilege", privilege.ToObject<string[]>().Join(","));
            ////}
            ////payload.Add("urn:weixin:user_info", payload.ToString());

            var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, document.RootElement);
            context.RunClaimActions();

            await Events.CreatingTicket(context);

            return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name);
        }
        #endregion

        #endregion

        #region "私有方法"
        #region "格式化作用域"
        /// <summary>
        /// 格式化作用域
        /// </summary>
        /// <returns></returns>
        protected override string FormatScope()
        {
            return string.Join(",", Options.Scope);
        }
        #endregion

        #endregion

    }
}
